<?php

// Prevent much happening if this script is called directly
if ( !defined('INCS') ) exit;

class FormElement
{
    // non-zero if any of the form elements
    // has a validation error.
    var $parent_errors;

    // markup rules
    var $accepts_value;

    // markup params
    var $wrapper_tag;
    var $wrapper_attributes;

    var $err_wrap_tag;
    var $err_wrap_attributes;
    var $err_content;
    var $err_field_transform;

    var $label;
    var $label_attributes;

    var $control_tagname;
    var $control_type;
    var $control_name;
    var $control_attributes;

    var $value;

    // Validation lookups
    var $validation_rules;
    var $error_messages;

    // validation result
    var $has_error;


    function FormElement( $name, $config )
    {

        $constructor_err = 'Error creating form element';

        if ( !isset($name) || !is_string($name) )
        {
            trigger_error( get_log_entry($constructor_err . ': expected name as string'), E_USER_WARNING);
            display_nice_error();
        }
        $this->control_name = $name;

        if ( !isset($config) || !is_array($config) )
        {
            trigger_error( get_log_entry($constructor_err . ': expected $config paramater as array'), E_USER_WARNING);
            display_nice_error();
        }

        if ( !isset($config['tagname']) || !is_string($config['tagname']) )
        {
            trigger_error( get_log_entry($constructor_err . ': expected tagname as string'), E_USER_WARNING);
            display_nice_error();
        }
        $this->control_tagname = $config['tagname'];

        if ( isset($config['wrapper_tag']) && is_string($config['wrapper_tag']) )
            $this->wrapper_tag = $config['wrapper_tag'];

        if ( isset($this->wrapper_tag) & isset($config['wrapper_attributes']) && is_string($config['wrapper_attributes']) )
            $this->wrapper_attributes = $config['wrapper_attributes'];

        if ( isset($config['label']) && is_string($config['label']) )
            $this->label = $config['label'];

        if ( isset($config['label_attributes']) && is_string($config['label_attributes']) )
            $this->label_attributes = $config['label_attributes'];

        if ( isset($config['type']) && is_string($config['type']) )
            $this->control_type = $config['type'];

        if ( isset($config['attributes']) && is_string($config['attributes']) )
            $this->control_attributes = $config['attributes'];

        if ( isset($config['err_wrap_tag']) && is_string($config['err_wrap_tag']) )
            $this->err_wrap_tag = $config['err_wrap_tag'];

        if ( isset($config['err_wrap_attributes']) && is_string($config['err_wrap_attributes']) )
            $this->err_wrap_attributes = $config['err_wrap_attributes'];

        if ( isset($config['err_field_transform']) && is_string($config['err_field_transform']) )
            $this->err_field_transform = $config['err_field_transform'];

        $this->check_markup_rules();
        $this->parent_errors =& Form::getInstance()->has_errors;

    }

    // TODO - refactor/replace/launch into outer space
    // Checks whether the form html element accepts a value and/or requires a
    // separate closing tag by looking-up the tag name and optional type in
    // lookup arrays. Sets the element class members accepts_value accordingly.
    //
    // $tags_with_value contains tagnames which can contain a 'value' attribute.
    // If only certain types of a given tag are applicable then
    // the array item for that tag should be a key value pair where the value is
    // an array of applicable types.
    //
    // @params none
    // @return void
    function check_markup_rules()
    {
        $tags_with_value = array( 'input' => array('text' => 1, 'hidden' => 1), 'option' => 1);

        $this->accepts_value = 0;
        if ( array_key_exists($this->control_tagname, $tags_with_value) )
        {
            if ( is_array($tags_with_value[$this->control_tagname]) && isset($this->control_type) )
            {
                if ( array_key_exists($this->control_type, $tags_with_value[$this->control_tagname]) )
                    $this->accepts_value = 1;

            } else {
                $this->accepts_value = 1;
            }
        }
    }



    public function validator( $incoming_var )
    {
        $err_code  = 0;
        $err_count = 0;

        foreach( $this->validation_rules as $rkey => $rval )
        {
            if ( !is_string($rkey) )
            {
                switch( $rval )
                {
                    case 'email':
                        $err_code = is_valid_email( $incoming_var );
                        break;

                    case 'non_empty':
                        $err_code = is_empty( $incoming_var );
                        break;
                }

            } else {

                switch( $rkey )
                {
                    case 'min_length':
                        $err_code = is_too_short( $incoming_var, $rval );
                        break;

                    case 'max_length':
                        $err_code = is_too_long( $incoming_var, $rval );
                        break;

                    default:
                        break;
                }

            }

            if ( $err_code )
            {
                $this->has_error = 1;

                // DEBUG
                if ( $err_code == VERR_UNSPECIFIED )
                {
                    trigger_error( get_log_entry('Error validating ' . $c->control_name), E_USER_WARNING);
                    display_nice_error();
                }
                if ( !isset($this->error_messages[$err_code]) || !is_string($this->error_messages[$err_code]) )
                {
                    trigger_error( get_log_entry($this->control_name . ' failed validation with code: '. $err_code), E_USER_WARNING);
                    display_nice_error();
                }


                // Set the error message. TODO Sanitize here????????????????
                $this->err_content = $this->error_messages[$err_code];
                $this->value = $incoming_var;
                return ++$err_count;
            }

            // Set the control's value. TODO Sanitize here????????????????
            $this->value = $incoming_var;

        }

        return $err_count;
    }

}



class InputElement extends FormElement
{

    // Constructor sets-up object according to the supplied config.
    // Checks that the config items are non-empty strings and assigns their
    // values to the object's properties.
    // Throws an error if required properties (name and tagname) cannot be set.
    // @param $config - array of configuration parameters.  see config.inc.php
    // @return void.
    function InputElement( $name, $config )
    {
        parent::__construct( $name, $config );

        // Briefly check we're set-up to validate this input...
        if ( !isset($config['validate']) || !is_array($config['validate']) )
        {
            trigger_error( get_log_entry($c->control_name . ' does not have validation rules associated with it.  Check your config.'), E_USER_WARNING);
            display_nice_error();
        }
        $this->validation_rules = $config['validate'];

        // ...and that we have error messages for invalid input
        if ( !isset($config['errors']) || !is_array($config['errors']) )
        {
            trigger_error( get_log_entry($c->control_name . ' does not have validation error messages defined.  Check your config.'), E_USER_WARNING);
            display_nice_error();
        }
        $this->error_messages = $config['errors'];

    }


    // Generates the html markup for this control object using its various
    // properties.
    // @param  int non-zero if the markup should represent an unused control.
    // @return string html markup for this control instance.
    function getMarkup()
    {
        $wrapper = new HTML_Element;
        $errmsg  = new HTML_Element;
        $label   = new HTML_Element;
        $control = new HTML_Element;
        $br      = new HTML_Element;

        if ( is_string($this->wrapper_tag) && !empty($this->wrapper_tag) )
        {
            $wrapper->tagname = $this->wrapper_tag;
            $wrapper->attributes = $this->wrapper_attributes;
        }
        if ( $this->parent_errors && is_string($this->err_content) && !empty($this->err_content) )
        {
            $errmsg->tagname = $this->err_wrap_tag;
            $errmsg->attributes = $this->err_wrap_attributes;
            $errmsg->innerValue = $this->err_content;
        }
        if ( is_string($this->label) && !empty($this->label) )
        {
            $label->tagname = 'label';
            $label->attributes = $this->label_attributes;
            $label->innerValue = $this->label;
        }

        $control->tagname = $this->control_tagname;
        $control->attributes = "name=\"{$this->control_name}\"";

        if ( $this->has_error && is_string($this->err_field_transform) && !empty($this->err_field_transform) )
        {
            $control->attributes .= " {$this->err_field_transform}";
        }
        else
        {
            $control->attributes .= " {$this->control_attributes}";
        }

        if ( $this->parent_errors && is_string($this->value) && !empty($this->value) )
        {
            if ( $this->accepts_value )
            {
                $v = html_encode($this->value);
                $control->attributes .= " value=\"$v\"";
            }
            else
            {
                $control->innerValue = html_encode($this->value);
            }
        }

        if ( $control->tagname == 'textarea' ) $br->tagname = 'br';

        // OK now get the markup!

        $wrapper->innerValue = $errmsg->getString() . $label->getString() . $br->getString() . $control->getString();

        return $wrapper->getString();

    }


}



class SelectorElement extends FormElement
{
    var $options;
    var $user_selection;

    // Constructor sets-up object according to the supplied config.
    // Checks that the config items are non-empty strings and assigns their
    // values to the object's properties.
    // Throws an error if required properties (name and tagname) cannot be set.
    // @param $config - array of configuration parameters.  see config.inc.php
    // @return void.
    function SelectorElement( $name, $config )
    {
        parent::__construct( $name, $config );

        // Briefly check we've got options for this selector.
        if ( !isset($config['options']) || !is_array($config['options']) )
        {
            trigger_error( get_log_entry($c->control_name . ' does not have options for the user to select.  Check your config.'), E_USER_WARNING);
            display_nice_error();
        }
        $this->options = $config['options'];
    }


    // Generates the html markup for this control object using its various
    // properties.
    // @return string html markup for this control instance.
    function getMarkup()
    {
        $wrapper   = new HTML_Element;
        $label     = new HTML_Element;
        $errborder = new HTML_Element;
        $control   = new HTML_Element;

        if ( is_string($this->wrapper_tag) && !empty($this->wrapper_tag) )
        {
            $wrapper->tagname = $this->wrapper_tag;
            $wrapper->attributes = $this->wrapper_attributes;
        }
        if ( is_string($this->label) && !empty($this->label) )
        {
            $label->tagname = 'label';
            $label->attributes = $this->label_attributes;
            $label->innerValue = $this->label;
        }
        $errborder->tagname = 'div';
        if ( $this->has_error && is_string($this->err_field_transform) && !empty($this->err_field_transform) )
        {
            $errborder->attributes = $this->err_field_transform;
        }

        $control->tagname = $this->control_tagname;
        $control->attributes = "name=\"{$this->control_name}\"";

        $opts;
        foreach( $this->options as $pos => $parts )
        {
            $o = new HTML_Element;
            $o->tagname = 'option';
            $o->attributes = "value=\"{$parts['nodeval']}\"";

            if ( (isset($parts['selected']) && !$this->user_selection ) ||
                $this->user_selection == $parts['nodeval'] ) $o->attributes .= ' selected="selected"';
            if ( isset($parts['attributes']) ) $o->attributes .= " {$parts['attributes']}";

            $o->innerValue = $parts['value'];
            $opts[] = $o->getString();
            unset($o);
        }

        $control->innerValue = implode($opts, "\n");
        $errborder->innerValue = $control->getString();

        $wrapper->innerValue = $label->getString() . $errborder->getString();

        return $wrapper->getString();

    }

    public function validator( $incoming_var )
    {
        $err_code;
        $err_count = 0;

        if ( !is_string($incoming_var) )
        {
            $err_code = VERR_OPT_INVALID;
        } else {

            switch( $incoming_var )
            {
                case 0:
                    $err_code = VERR_OPT_UNSELECT;
                    break;
                case 1:
                case 2:
                case 3:
                    $err_code = VERR_NONE;
                    break;
                default:
                    $err_code = VERR_OPT_INVALID;
            }
        }

        if ( $err_code )
        {
            $err_count = 1;
            $this->user_selection = 0;
        } else {
            Form::getInstance()->recipient = $incoming_var;
            $this->user_selection = $incoming_var;
        }

        $this->has_error = $err_code;

        return $err_count;
    }
}




class HiddenElement extends InputElement
{

    function getMarkup()
    {

        $hidden = new HTML_Element;

        $hidden->tagname = $this->control_tagname;
        $hidden->attributes = "name=\"{$this->control_name}\"";
        $hidden->attributes .= " {$this->control_attributes}";

        if ( $this->accepts_value && isset($_SESSION['token']) )
        {
            $hidden->attributes .= " value=\"{$_SESSION['token']}\"";
        }

        return $hidden->getString();
    }
}
